<?php
/**
 * object instance storage tools interface
 * + flat files base class implementation
 *
 * This file is part of Webappkit, a packages system for PHP web apps
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage storages
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */

/**
* Base class for webappkit object instances storages.
* generic filesystem-based storage.
* Use search patterns as path template to retrieve a file by search criteria,
* then build an object instance from the file's content.
* Search patterns may contain strings between double accolads, including an optional criteria name as below
* {optional-part{criteria}optional part}
* If criteria has a value, it will be replaced by its value surrounded by optional parts and accolads will be removed
* Else, the wole string will be removed from search pattern
* @author J.Ducastel <nospam0@ducastel.name>
* @todo criteria validation (regex ?)
*/
class WakStorage extends wakTool {
/*--------------------------------------------------------------------
 STATIC METHODS
--------------------------------------------------------------------*/
    /**
     * factory pattern, use this method to build instances
     * all subclasses have to implement this method
     * because of PHP object model limitations
     * @static
     * @param string $kit_id parent kit id
     * @param array $cfg configuration data, will be merged with defaults
     * @return wakTool
     */
    function toolFactory($kit_id,$cfg=array()) {
        return new WakStorage($kit_id,$cfg);
    }
/*--------------------------------------------------------------------
CONSTRUCTOR
--------------------------------------------------------------------*/

/*--------------------------------------------------------------------
PUBLIC INSTANCE METHODS
--------------------------------------------------------------------*/

    function get($id) {
        // search file from pattern
        $file=$this->_makeFileName($id); // echo "<p>file = $file</p>";
        if (!$file or !file_exists($this->getPath().$file))
            return false;
        // reading file content
        $content=file_get_contents($this->getPath().$file); // echo $content;
        // building instance
        $instance=unserialize($content); // echo $content;
        return $instance;
    }

    /**
     * search for an item
     * @access public
     * @param array $criteria
     * @return bool
     */
    function has($id) {
        $file=$this->getPath().$this->_makeFileName($id); //echo "<p>file = $file</p>";
        return file_exists($file);
    }
    /**
     * alias for hasItem
     */
    function hasItem($id) {
        return $this->has($id);
    }

    function search($criteria) {

    }

    function save(&$instance) {
        // building file name
        $key=$this->getIdMember();
        $id=$instance->$key; // echo $id;
        $file=$this->getPath().$this->_makeFileName($id);
        // serialazing for storage
        $serialized=serialize($instance);
        // deleting then saving
        /*if (file_exists($file) and !unlink($file))
            return false;*/
        $h=fopen($file,'w');
        return fwrite($h,$serialized);
    }

    function delete($id) {
        // building file name
        $file=$this->getPath().$this->_makeFileName($id);
        // deleting then saving
        return unlink($file);
    }

    function getClass() {
        return $this->cfg['storage']['class'];
    }

    function getIdMember() {
        return $this->cfg['storage']['id_member'];
    }

    function getPath() {
        $path=Webappkit::getPathFromId($this->getKitId());
        return $path;
    }

    /**
    * sets new default value for a criteria
    * @param string $key
    * @param string $value
    * @access public
    * @return bool
    */
    function setCriterium($key,$value) {
        $this->cfg['criteria'][$key]=$value;
        return true;
    }/**/

    function setCriteria($key,$value) {
        return $this->setCriterium($key,$value);
    }

    /**
    * get search pattern
    * @access public
    * @return string
    */
    function getSearchPattern() {
        return $this->cfg['storage']['filenames'];
    }

    /**
    * set search pattern
    * @access public
    * @param string $pattern
    * @return bool
    */
    function setSearchPattern($pattern) {
        if (!is_string($pattern))
            return false;
        $this->cfg['storage']['filenames']=$pattern;
        return true;
    }
/*--------------------------------------------------------------------
VARS / MEMBERS
--------------------------------------------------------------------*/
    /**
     * @var array default configuration to be overrided
     * [storage][search] => $this->search search pattern
     * [storage][path]
     * [storage][class] => $this->stored_class stored objects class
     * [storage][id_member] object's unique key member
     * [criteria] => $this->criteria
     */
    var $cfg=array(
        'storage'=>array(
            'filenames'=>'{{id}}.txt'
            ,'class'=>'' // stored objects class
            ,'id_member'=>'id' //
        ),'criteria'=>array() // default values for search criteria
    );

    /**
     * Merge passed search criteria with defaults
     * @access protected
     * @param array $criteria associative array
     * @return array
     */
    function _fillCriteria($criteria=array()) {
        // string criteria = id
        /*if (is_string($criteria)) {
            $criteria=array('id'=>$criteria);
        }*/
        $defaults=isset($this->cfg['criteria'])?$this->cfg['criteria']:array();
        // looping defaults
        foreach ($defaults as $key => $value) {
            // setting default if not set in arg
            if (!isset($criteria[$key]))
                $criteria[$key]=$value;
        }
        return $criteria;
    }/**/

    /**
     * injects criteria into search template to get filename.
     * Replace var's key by their actual value within search template.
     * Scheme is {optional part before{key}optional part after}
     * checks that file exists.
     * @access protected
     * @param string $id
     * @return string filename or false if not found
     */
    function _makeFileName($id) {
        // getting full criteria
        $criteria=$this->_fillCriteria(array('id'=>$id)); // print_r($criteria);
        $n=0;
        $regex='\{([^{}]*)\{([a-zA-Z][a-zA-Z0-9_\.-]*)\}([^{}]*)\}';
        // echo $regex;
        $filename=$this->getSearchPattern();
        while (ereg($regex,$filename,$found)) {
            // avoiding infinite loop
            $n++; if ($n>50) break;
            // print_r($found);
            $before=$found[1];
            $key=$found[2];
            //$force=$found[3];
            $after=$found[3];
            // building replacement block
            if (isset($criteria[$key])
            /*and is_string($criteria[$key])*/
            and $criteria[$key]) {
                // provided, not empty string value
                $replace=$before.$criteria[$key].$after;
            } else {
                // not provided or null value
                $replace='';
            }
            // replacing
            // escaping regex special chars
            $before=str_replace('(','\(',$before);
            $before=str_replace(')','\)',$before);
            $after=str_replace('(','\(',$after);
            $after=str_replace(')','\)',$after);
            // replacing regex
            $ereg2='\{'.$before.'\{'.$key.'\}'.$after.'\}';
            $filename=ereg_replace($ereg2,$replace,$filename);
        }
        // adding kit path
        return $filename;
    }/**/
}
